package queue

import (
	"fmt"
	"math"
	"math/rand"
	"sort"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/cortexproject/cortex/pkg/util/validation"
)

func TestQueues(t *testing.T) {
	uq := newUserQueues(0, MockLimits{}, nil)
	assert.NotNil(t, uq)
	assert.NoError(t, isConsistent(uq))

	q, u, lastUserIndex := uq.getNextQueueForQuerier(-1, "querier-1")
	assert.Nil(t, q)
	assert.Equal(t, "", u)

	// Add queues: [one]
	qOne := getOrAdd(t, uq, "one", 0)
	lastUserIndex = confirmOrderForQuerier(t, uq, "querier-1", lastUserIndex, qOne, qOne)

	// [one two]
	qTwo := getOrAdd(t, uq, "two", 0)
	assert.NotEqual(t, qOne, qTwo)

	lastUserIndex = confirmOrderForQuerier(t, uq, "querier-1", lastUserIndex, qTwo, qOne, qTwo, qOne)
	confirmOrderForQuerier(t, uq, "querier-2", -1, qOne, qTwo, qOne)

	// [one two three]
	// confirm fifo by adding a third queue and iterating to it
	qThree := getOrAdd(t, uq, "three", 0)

	lastUserIndex = confirmOrderForQuerier(t, uq, "querier-1", lastUserIndex, qTwo, qThree, qOne)

	// Remove one: ["" two three]
	uq.deleteQueue("one")
	assert.NoError(t, isConsistent(uq))

	lastUserIndex = confirmOrderForQuerier(t, uq, "querier-1", lastUserIndex, qTwo, qThree, qTwo)

	// "four" is added at the beginning of the list: [four two three]
	qFour := getOrAdd(t, uq, "four", 0)

	lastUserIndex = confirmOrderForQuerier(t, uq, "querier-1", lastUserIndex, qThree, qFour, qTwo, qThree)

	// Remove two: [four "" three]
	uq.deleteQueue("two")
	assert.NoError(t, isConsistent(uq))

	lastUserIndex = confirmOrderForQuerier(t, uq, "querier-1", lastUserIndex, qFour, qThree, qFour)

	// Remove three: [four]
	uq.deleteQueue("three")
	assert.NoError(t, isConsistent(uq))

	// Remove four: []
	uq.deleteQueue("four")
	assert.NoError(t, isConsistent(uq))

	q, _, _ = uq.getNextQueueForQuerier(lastUserIndex, "querier-1")
	assert.Nil(t, q)
}

func TestQueuesWithQueriers(t *testing.T) {
	uq := newUserQueues(0, MockLimits{}, nil)
	assert.NotNil(t, uq)
	assert.NoError(t, isConsistent(uq))

	queriers := 30
	users := 1000
	maxQueriersPerUser := 5

	// Add some queriers.
	for ix := range queriers {
		qid := fmt.Sprintf("querier-%d", ix)
		uq.addQuerierConnection(qid)

		// No querier has any queues yet.
		q, u, _ := uq.getNextQueueForQuerier(-1, qid)
		assert.Nil(t, q)
		assert.Equal(t, "", u)
	}

	assert.NoError(t, isConsistent(uq))

	// Add user queues.
	for u := range users {
		uid := fmt.Sprintf("user-%d", u)
		getOrAdd(t, uq, uid, maxQueriersPerUser)

		// Verify it has maxQueriersPerUser queriers assigned now.
		qs := uq.userQueues[uid].queriers
		assert.Equal(t, maxQueriersPerUser, len(qs))
	}

	// After adding all users, verify results. For each querier, find out how many different users it handles,
	// and compute mean and stdDev.
	queriersMap := make(map[string]int)

	for q := range queriers {
		qid := fmt.Sprintf("querier-%d", q)

		lastUserIndex := -1
		for {
			_, _, newIx := uq.getNextQueueForQuerier(lastUserIndex, qid)
			if newIx < lastUserIndex {
				break
			}
			lastUserIndex = newIx
			queriersMap[qid]++
		}
	}

	mean := float64(0)
	for _, c := range queriersMap {
		mean += float64(c)
	}
	mean = mean / float64(len(queriersMap))

	stdDev := float64(0)
	for _, c := range queriersMap {
		d := float64(c) - mean
		stdDev += (d * d)
	}
	stdDev = math.Sqrt(stdDev / float64(len(queriersMap)))
	t.Log("mean:", mean, "stddev:", stdDev)

	assert.InDelta(t, users*maxQueriersPerUser/queriers, mean, 1)
	assert.InDelta(t, stdDev, 0, mean*0.2)
}

func TestQueuesConsistency(t *testing.T) {
	tests := map[string]struct {
		forgetDelay time.Duration
	}{
		"without forget delay": {},
		"with forget delay":    {forgetDelay: time.Minute},
	}

	for testName, testData := range tests {
		t.Run(testName, func(t *testing.T) {
			uq := newUserQueues(testData.forgetDelay, MockLimits{}, nil)
			assert.NotNil(t, uq)
			assert.NoError(t, isConsistent(uq))

			r := rand.New(rand.NewSource(time.Now().Unix()))

			lastUserIndexes := map[string]int{}

			conns := map[string]int{}

			for i := range 10000 {
				switch r.Int() % 6 {
				case 0:
					assert.NotNil(t, uq.getOrAddQueue(generateTenant(r), 3))
				case 1:
					qid := generateQuerier(r)
					_, _, luid := uq.getNextQueueForQuerier(lastUserIndexes[qid], qid)
					lastUserIndexes[qid] = luid
				case 2:
					uq.deleteQueue(generateTenant(r))
				case 3:
					q := generateQuerier(r)
					uq.addQuerierConnection(q)
					conns[q]++
				case 4:
					q := generateQuerier(r)
					if conns[q] > 0 {
						uq.removeQuerierConnection(q, time.Now())
						conns[q]--
					}
				case 5:
					q := generateQuerier(r)
					uq.notifyQuerierShutdown(q)
				}

				assert.NoErrorf(t, isConsistent(uq), "last action %d", i)
			}
		})
	}
}

func TestQueues_ForgetDelay(t *testing.T) {
	const (
		forgetDelay        = time.Minute
		maxQueriersPerUser = 1
		numUsers           = 100
	)

	now := time.Now()
	uq := newUserQueues(forgetDelay, MockLimits{}, nil)
	assert.NotNil(t, uq)
	assert.NoError(t, isConsistent(uq))

	// 3 queriers open 2 connections each.
	for i := 1; i <= 3; i++ {
		uq.addQuerierConnection(fmt.Sprintf("querier-%d", i))
		uq.addQuerierConnection(fmt.Sprintf("querier-%d", i))
	}

	// Add user queues.
	for i := range numUsers {
		userID := fmt.Sprintf("user-%d", i)
		getOrAdd(t, uq, userID, maxQueriersPerUser)
	}

	// We expect querier-1 to have some users.
	querier1Users := getUsersByQuerier(uq, "querier-1")
	require.NotEmpty(t, querier1Users)

	// Gracefully shutdown querier-1.
	uq.removeQuerierConnection("querier-1", now.Add(20*time.Second))
	uq.removeQuerierConnection("querier-1", now.Add(21*time.Second))
	uq.notifyQuerierShutdown("querier-1")

	// We expect querier-1 has been removed.
	assert.NotContains(t, uq.queriers, "querier-1")
	assert.NoError(t, isConsistent(uq))

	// We expect querier-1 users have been shuffled to other queriers.
	for _, userID := range querier1Users {
		assert.Contains(t, append(getUsersByQuerier(uq, "querier-2"), getUsersByQuerier(uq, "querier-3")...), userID)
	}

	// Querier-1 reconnects.
	uq.addQuerierConnection("querier-1")
	uq.addQuerierConnection("querier-1")

	// We expect the initial querier-1 users have got back to querier-1.
	for _, userID := range querier1Users {
		assert.Contains(t, getUsersByQuerier(uq, "querier-1"), userID)
		assert.NotContains(t, getUsersByQuerier(uq, "querier-2"), userID)
		assert.NotContains(t, getUsersByQuerier(uq, "querier-3"), userID)
	}

	// Querier-1 abruptly terminates (no shutdown notification received).
	uq.removeQuerierConnection("querier-1", now.Add(40*time.Second))
	uq.removeQuerierConnection("querier-1", now.Add(41*time.Second))

	// We expect querier-1 has NOT been removed.
	assert.Contains(t, uq.queriers, "querier-1")
	assert.NoError(t, isConsistent(uq))

	// We expect the querier-1 users have not been shuffled to other queriers.
	for _, userID := range querier1Users {
		assert.Contains(t, getUsersByQuerier(uq, "querier-1"), userID)
		assert.NotContains(t, getUsersByQuerier(uq, "querier-2"), userID)
		assert.NotContains(t, getUsersByQuerier(uq, "querier-3"), userID)
	}

	// Try to forget disconnected queriers, but querier-1 forget delay hasn't passed yet.
	uq.forgetDisconnectedQueriers(now.Add(90 * time.Second))

	assert.Contains(t, uq.queriers, "querier-1")
	assert.NoError(t, isConsistent(uq))

	for _, userID := range querier1Users {
		assert.Contains(t, getUsersByQuerier(uq, "querier-1"), userID)
		assert.NotContains(t, getUsersByQuerier(uq, "querier-2"), userID)
		assert.NotContains(t, getUsersByQuerier(uq, "querier-3"), userID)
	}

	// Try to forget disconnected queriers. This time querier-1 forget delay has passed.
	uq.forgetDisconnectedQueriers(now.Add(105 * time.Second))

	assert.NotContains(t, uq.queriers, "querier-1")
	assert.NoError(t, isConsistent(uq))

	// We expect querier-1 users have been shuffled to other queriers.
	for _, userID := range querier1Users {
		assert.Contains(t, append(getUsersByQuerier(uq, "querier-2"), getUsersByQuerier(uq, "querier-3")...), userID)
	}
}

func TestQueues_ForgetDelay_ShouldCorrectlyHandleQuerierReconnectingBeforeForgetDelayIsPassed(t *testing.T) {
	const (
		forgetDelay        = time.Minute
		maxQueriersPerUser = 1
		numUsers           = 100
	)

	now := time.Now()
	uq := newUserQueues(forgetDelay, MockLimits{}, nil)
	assert.NotNil(t, uq)
	assert.NoError(t, isConsistent(uq))

	// 3 queriers open 2 connections each.
	for i := 1; i <= 3; i++ {
		uq.addQuerierConnection(fmt.Sprintf("querier-%d", i))
		uq.addQuerierConnection(fmt.Sprintf("querier-%d", i))
	}

	// Add user queues.
	for i := range numUsers {
		userID := fmt.Sprintf("user-%d", i)
		getOrAdd(t, uq, userID, maxQueriersPerUser)
	}

	// We expect querier-1 to have some users.
	querier1Users := getUsersByQuerier(uq, "querier-1")
	require.NotEmpty(t, querier1Users)

	// Querier-1 abruptly terminates (no shutdown notification received).
	uq.removeQuerierConnection("querier-1", now.Add(40*time.Second))
	uq.removeQuerierConnection("querier-1", now.Add(41*time.Second))

	// We expect querier-1 has NOT been removed.
	assert.Contains(t, uq.queriers, "querier-1")
	assert.NoError(t, isConsistent(uq))

	// We expect the querier-1 users have not been shuffled to other queriers.
	for _, userID := range querier1Users {
		assert.Contains(t, getUsersByQuerier(uq, "querier-1"), userID)
		assert.NotContains(t, getUsersByQuerier(uq, "querier-2"), userID)
		assert.NotContains(t, getUsersByQuerier(uq, "querier-3"), userID)
	}

	// Try to forget disconnected queriers, but querier-1 forget delay hasn't passed yet.
	uq.forgetDisconnectedQueriers(now.Add(90 * time.Second))

	// Querier-1 reconnects.
	uq.addQuerierConnection("querier-1")
	uq.addQuerierConnection("querier-1")

	assert.Contains(t, uq.queriers, "querier-1")
	assert.NoError(t, isConsistent(uq))

	// We expect the querier-1 users have not been shuffled to other queriers.
	for _, userID := range querier1Users {
		assert.Contains(t, getUsersByQuerier(uq, "querier-1"), userID)
		assert.NotContains(t, getUsersByQuerier(uq, "querier-2"), userID)
		assert.NotContains(t, getUsersByQuerier(uq, "querier-3"), userID)
	}

	// Try to forget disconnected queriers far in the future, but there's no disconnected querier.
	uq.forgetDisconnectedQueriers(now.Add(200 * time.Second))

	assert.Contains(t, uq.queriers, "querier-1")
	assert.NoError(t, isConsistent(uq))

	for _, userID := range querier1Users {
		assert.Contains(t, getUsersByQuerier(uq, "querier-1"), userID)
		assert.NotContains(t, getUsersByQuerier(uq, "querier-2"), userID)
		assert.NotContains(t, getUsersByQuerier(uq, "querier-3"), userID)
	}
}

func TestGetOrAddQueueShouldUpdateProperties(t *testing.T) {
	limits := MockLimits{
		MaxOutstanding: 3,
	}
	q := newUserQueues(0, limits, nil)
	q.addQuerierConnection("q-1")
	q.addQuerierConnection("q-2")
	q.addQuerierConnection("q-3")
	q.addQuerierConnection("q-4")
	q.addQuerierConnection("q-5")

	queue := q.getOrAddQueue("userID", 2)
	queue.enqueueRequest(MockRequest{})

	assert.NotNil(t, q.userQueues["userID"])
	assert.Equal(t, 3, q.userQueues["userID"].maxOutstanding)
	assert.Equal(t, 2, q.userQueues["userID"].maxQueriers)
	assert.Equal(t, 5, len(q.queriers))
	assert.Equal(t, 2, len(q.userQueues["userID"].queriers))
	assert.Subset(t, getKeys(q.queriers), getKeys(q.userQueues["userID"].queriers))
	assert.IsType(t, &FIFORequestQueue{}, queue)
	assert.Equal(t, 1, queue.length())

	limits.MaxOutstanding = 10
	q.limits = limits
	queue = q.getOrAddQueue("userID", 0)

	assert.Equal(t, 10, q.userQueues["userID"].maxOutstanding)
	assert.Equal(t, 0, q.userQueues["userID"].maxQueriers)
	assert.Nil(t, q.userQueues["userID"].queriers)
	assert.IsType(t, &FIFORequestQueue{}, queue)
	assert.Equal(t, 1, queue.length())

	limits.QueryPriorityVal = validation.QueryPriority{Enabled: true, Priorities: []validation.PriorityDef{
		{
			Priority:         1,
			ReservedQueriers: 2,
		},
	}}
	q.limits = limits
	queue = q.getOrAddQueue("userID", 3)

	assert.Equal(t, 10, q.userQueues["userID"].maxOutstanding)
	assert.Equal(t, 3, q.userQueues["userID"].maxQueriers)
	assert.Equal(t, 5, len(q.queriers))
	assert.Equal(t, 3, len(q.userQueues["userID"].queriers))
	assert.Equal(t, 2, len(q.userQueues["userID"].reservedQueriers))
	assert.IsType(t, &PriorityRequestQueue{}, queue)
	assert.Equal(t, 1, queue.length())
	assert.ElementsMatch(t, []int64{1, 1}, q.userQueues["userID"].priorityList)
	assert.True(t, q.userQueues["userID"].priorityEnabled)
	assert.Subset(t, getKeys(q.queriers), getKeys(q.userQueues["userID"].queriers))
	assert.Subset(t, getKeys(q.userQueues["userID"].queriers), getKeys(q.userQueues["userID"].reservedQueriers))

	limits.QueryPriorityVal = validation.QueryPriority{Enabled: true, Priorities: []validation.PriorityDef{
		{
			Priority:         1,
			ReservedQueriers: 0.5,
		},
	}}
	q.limits = limits
	_ = q.getOrAddQueue("userID", 3)

	assert.Equal(t, 10, q.userQueues["userID"].maxOutstanding)
	assert.Equal(t, 3, q.userQueues["userID"].maxQueriers)
	assert.Equal(t, 5, len(q.queriers))
	assert.Equal(t, 3, len(q.userQueues["userID"].queriers))
	assert.Equal(t, 2, len(q.userQueues["userID"].reservedQueriers))
	assert.ElementsMatch(t, []int64{1, 1}, q.userQueues["userID"].priorityList)

	limits.QueryPriorityVal = validation.QueryPriority{Enabled: true, Priorities: []validation.PriorityDef{
		{
			Priority:         1,
			ReservedQueriers: float64(q.userQueues["userID"].maxQueriers),
		},
	}}
	q.limits = limits
	_ = q.getOrAddQueue("userID", 3)

	assert.Equal(t, 10, q.userQueues["userID"].maxOutstanding)
	assert.Equal(t, 3, q.userQueues["userID"].maxQueriers)
	assert.Equal(t, 5, len(q.queriers))
	assert.Equal(t, 3, len(q.userQueues["userID"].queriers))
	assert.Equal(t, 0, len(q.userQueues["userID"].reservedQueriers))
	assert.ElementsMatch(t, []int64{}, q.userQueues["userID"].priorityList)

	limits.QueryPriorityVal.Enabled = false
	q.limits = limits
	queue = q.getOrAddQueue("userID", 3)
	assert.IsType(t, &FIFORequestQueue{}, queue)

	// check the queriers and reservedQueriers map are consistent
	for range 100 {
		queriers := q.userQueues["userID"].queriers
		reservedQueriers := q.userQueues["userID"].reservedQueriers
		q.userQueues["userID"].maxQueriers = 0          // reset to trigger querier assignment
		q.userQueues["userID"].priorityList = []int64{} // reset to trigger reserved querier assignment
		_ = q.getOrAddQueue("userID", 3)

		assert.Equal(t, queriers, q.userQueues["userID"].queriers)
		assert.Equal(t, reservedQueriers, q.userQueues["userID"].reservedQueriers)
	}
}

func TestQueueConcurrency(t *testing.T) {
	const numGoRoutines = 30
	limits := MockLimits{
		MaxOutstanding: 50,
	}
	q := newUserQueues(0, limits, nil)
	q.addQuerierConnection("q-1")
	q.addQuerierConnection("q-2")
	q.addQuerierConnection("q-3")
	q.addQuerierConnection("q-4")
	q.addQuerierConnection("q-5")

	var wg sync.WaitGroup
	wg.Add(numGoRoutines)

	for i := range numGoRoutines {
		go func(cnt int) {
			defer wg.Done()
			queue := q.getOrAddQueue("userID", 2)
			if cnt%2 == 0 {
				queue.enqueueRequest(MockRequest{})
				q.getNextQueueForQuerier(0, "q-1")
			} else if cnt%5 == 0 {
				queue.dequeueRequest(0, false)
			} else if cnt%7 == 0 {
				q.deleteQueue("userID")
			}
		}(i)
	}

	wg.Wait()
}

func generateTenant(r *rand.Rand) string {
	return fmt.Sprint("tenant-", r.Int()%5)
}

func generateQuerier(r *rand.Rand) string {
	return fmt.Sprint("querier-", r.Int()%5)
}

func getOrAdd(t *testing.T, uq *queues, tenant string, maxQueriers int) userRequestQueue {
	q := uq.getOrAddQueue(tenant, maxQueriers)
	assert.NotNil(t, q)
	assert.NoError(t, isConsistent(uq))
	assert.Equal(t, q, uq.getOrAddQueue(tenant, maxQueriers))
	return q
}

func confirmOrderForQuerier(t *testing.T, uq *queues, querier string, lastUserIndex int, qs ...userRequestQueue) int {
	var n userRequestQueue
	for _, q := range qs {
		n, _, lastUserIndex = uq.getNextQueueForQuerier(lastUserIndex, querier)
		assert.Equal(t, q, n)
		assert.NoError(t, isConsistent(uq))
	}
	return lastUserIndex
}

func isConsistent(uq *queues) error {
	if len(uq.sortedQueriers) != len(uq.queriers) {
		return fmt.Errorf("inconsistent number of sorted queriers and querier connections")
	}

	uc := 0
	for ix, u := range uq.users {
		q := uq.userQueues[u]
		if u != "" && q == nil {
			return fmt.Errorf("user %s doesn't have queue", u)
		}
		if u == "" && q != nil {
			return fmt.Errorf("user %s shouldn't have queue", u)
		}
		if u == "" {
			continue
		}

		uc++

		if q.index != ix {
			return fmt.Errorf("invalid user's index, expected=%d, got=%d", ix, q.index)
		}

		if q.maxQueriers == 0 && q.queriers != nil {
			return fmt.Errorf("user %s has queriers, but maxQueriers=0", u)
		}

		if q.maxQueriers > 0 && len(uq.sortedQueriers) <= q.maxQueriers && q.queriers != nil {
			return fmt.Errorf("user %s has queriers set despite not enough queriers available", u)
		}

		if q.maxQueriers > 0 && len(uq.sortedQueriers) > q.maxQueriers && len(q.queriers) != q.maxQueriers {
			return fmt.Errorf("user %s has incorrect number of queriers, expected=%d, got=%d", u, len(q.queriers), q.maxQueriers)
		}
	}

	if uc != len(uq.userQueues) {
		return fmt.Errorf("inconsistent number of users list and user queues")
	}

	return nil
}

// getUsersByQuerier returns the list of users handled by the provided querierID.
func getUsersByQuerier(queues *queues, querierID string) []string {
	var userIDs []string
	for userID, q := range queues.userQueues {
		if q.queriers == nil {
			// If it's nil then all queriers can handle this user.
			userIDs = append(userIDs, userID)
			continue
		}
		if _, ok := q.queriers[querierID]; ok {
			userIDs = append(userIDs, userID)
		}
	}
	return userIDs
}

func getKeys(x any) []string {
	var keys []string

	switch i := x.(type) {
	case map[string]struct{}:
		for k := range i {
			keys = append(keys, k)
		}
	case map[string]*querier:
		for k := range i {
			keys = append(keys, k)
		}
	case map[string]int64:
		for k := range i {
			keys = append(keys, k)
		}
	}

	return keys
}

func TestShuffleQueriers(t *testing.T) {
	allQueriers := []string{"a", "b", "c", "d", "e"}

	queriers := shuffleQueriersForUser(12345, 10, allQueriers, nil)
	require.Nil(t, queriers)

	queriers = shuffleQueriersForUser(12345, len(allQueriers), allQueriers, nil)
	require.Nil(t, queriers)

	r1 := shuffleQueriersForUser(12345, 3, allQueriers, nil)
	require.Equal(t, 3, len(r1))

	// Same input produces same output.
	r2 := shuffleQueriersForUser(12345, 3, allQueriers, nil)
	require.Equal(t, 3, len(r2))
	require.Equal(t, r1, r2)
}

func TestShuffleQueriersCorrectness(t *testing.T) {
	const queriersCount = 100

	var allSortedQueriers []string
	for i := range queriersCount {
		allSortedQueriers = append(allSortedQueriers, fmt.Sprintf("%d", i))
	}
	sort.Strings(allSortedQueriers)

	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	const tests = 1000
	for range tests {
		toSelect := r.Intn(queriersCount)
		if toSelect == 0 {
			toSelect = 3
		}

		selected := shuffleQueriersForUser(r.Int63(), toSelect, allSortedQueriers, nil)

		require.Equal(t, toSelect, len(selected))

		sort.Strings(allSortedQueriers)
		prevQuerier := ""
		for _, q := range allSortedQueriers {
			require.True(t, prevQuerier < q, "non-unique querier")
			prevQuerier = q

			ix := sort.SearchStrings(allSortedQueriers, q)
			require.True(t, ix < len(allSortedQueriers) && allSortedQueriers[ix] == q, "selected querier is not between all queriers")
		}
	}
}

func TestHasPriorityListChanged(t *testing.T) {
	require.True(t, hasPriorityListChanged([]int64{1, 2}, []int64{1, 3}))
	require.False(t, hasPriorityListChanged([]int64{1, 2}, []int64{1, 2}))
	require.True(t, hasPriorityListChanged([]int64{1, 2}, []int64{1}))
	require.False(t, hasPriorityListChanged([]int64{}, []int64{}))
}

func TestGetPriorityList(t *testing.T) {
	queryPriority := validation.QueryPriority{
		Enabled: true,
		Priorities: []validation.PriorityDef{
			{
				Priority:         1,
				ReservedQueriers: 2,
			},
			{
				Priority:         2,
				ReservedQueriers: 3,
			},
		},
	}

	assert.EqualValues(t, []int64{1, 1, 2, 2, 2}, getPriorityList(queryPriority, 10))
	assert.EqualValues(t, []int64{}, getPriorityList(queryPriority, 1))

	queryPriority.Priorities[0].ReservedQueriers = 0.3
	queryPriority.Priorities[1].ReservedQueriers = 0.6
	assert.EqualValues(t, []int64{1, 1, 1, 2, 2, 2, 2, 2, 2}, getPriorityList(queryPriority, 10))

	// Cannot reserve all queriers for prioritized requests, remote read and metadata queries also need capacity.
	queryPriority.Priorities[0].ReservedQueriers = 0.4
	queryPriority.Priorities[1].ReservedQueriers = 0.6
	assert.EqualValues(t, []int64{}, getPriorityList(queryPriority, 10))

	queryPriority.Enabled = false
	assert.Nil(t, getPriorityList(queryPriority, 10))
}
